﻿//-------------------------------------------------------------------------------------------------
// <copyright file="OfficeInterop.cs" company="Anthony Adam">
// Copyright (c) Anthony Adam 2012. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

// *** Set the two office compilation directives according to your personal environment before building ***

// define this if you have office 2007 installed to build with office interops
// higher office versions, you will need to check the references to: 
// Microsoft.Office.Core
// Microsoft.Office.Interop.Excel
// Microsoft.Office.Interop.Word
// are all valid for your build or update them appropriately.

#define I_HAVE_OFFICE

// define this if you have OpenOffice 3.x installed, the libraries in this
// source pack should work with OpenOffice 3.x

#define I_HAVE_OPEN_OFFICE

// ReSharper disable once CheckNamespace
namespace SuperStarRounds
{
  using System;
  using System.IO;
  using System.Reflection;
  using System.Runtime.InteropServices;
  using System.Windows.Forms;

  using Microsoft.Win32;

#if (I_HAVE_OFFICE)
  using Excel = Microsoft.Office.Interop.Excel; // get round System.Windows.Forms conflicts by renaming
  using Word = Microsoft.Office.Interop.Word; // get round System.Windows.Forms conflicts by renaming
#endif

  /// <summary>
  /// Utility class for office and openoffice interoperation
  /// on the development machine.
  /// </summary>
  public static class OfficeInterop
  {
    private static readonly object TypeMissing = Missing.Value;

    /// <summary>
    /// Storage of path to the open office GAC entries.
    /// </summary>
    private static string myOpenOfficePath;

    /// <summary>
    /// Test if Microsoft Word is installed - caller must catch Exceptions to handle Interop issues
    /// at the application call point.
    /// </summary>
    /// <returns>True if Word found, else False</returns>
    public static bool IsWordInstalled()
    {
#if (I_HAVE_OFFICE)
      Type officeType = Type.GetTypeFromProgID("Word.Application");
      return officeType != null;
#else
      return false;
#endif
    }

    /// <summary>
    /// Test if Microsoft Excel is installed - caller must catch Exceptions to handle Interop issues
    /// at the application call point.
    /// </summary>
    /// <returns>True if Excel found, else False</returns>
    public static bool IsExcelInstalled()
    {
#if (I_HAVE_OFFICE)
      Type officeType = Type.GetTypeFromProgID("Excel.Application");
      return officeType != null;
#else
      return false;
#endif
    }

    /// <summary>
    /// Test if OpenOffice is installed - caller must catch Exceptions to handle Interop issues 
    /// Also, if installed, set up OpenOffice environment variables for correct Paths per OpenOffice API
    /// </summary>
    /// <returns>True if OpenOffice found, else False</returns>
    public static bool IsOpenOfficeInstalled()
    {
#if (I_HAVE_OPEN_OFFICE)
      const string softwareKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
      using (var rk = Registry.LocalMachine.OpenSubKey(softwareKey))
      {
        if (rk != null)
          foreach (var subKeyName in rk.GetSubKeyNames())
          {
            using (var subKey = rk.OpenSubKey(subKeyName))
            {
              myOpenOfficePath = string.Empty;

              if (subKey?.GetValue("Path") != null)
                myOpenOfficePath = subKey.GetValue("Path").ToString();

              if (string.IsNullOrEmpty(myOpenOfficePath)) continue;
              if (!myOpenOfficePath.Contains("OpenOffice")) continue;

              // found it, so it is installed - lets update the environment appropriately
              try
              {
                InitialiseOpenOfficeEnvironment();
                return true;
              }
              catch (Exception)
              {
                // if we fail to set the environment, assume the OO install is corrupt in
                // some way and exit as if not found - that way the app gracefully doesnt
                // whinge at the user for something they may not be aware of.
                myOpenOfficePath = string.Empty;
                return false;
              }
            }
          }
      }
#endif

      // set the path to empty as we didnt find open office anywhere.
      myOpenOfficePath = string.Empty;
      return false;
    }

    /// <summary>
    /// Place richtextbox content to a new Word document
    /// Caller must catch exceptions and deal with at application call point
    /// </summary>
    /// <param name="toBeExported">The RichTextBox control with text to be exported</param>
    public static void ExportRichTextToMicrosoftWord(RichTextBox toBeExported)
    {
#if (I_HAVE_OFFICE)
      Word.Application wordInstance;
      try
      {
        // try using an open instance if one is available
        wordInstance = (Word.Application)Marshal.GetActiveObject("Word.Application");
      }
      catch (COMException)
      {
        wordInstance = new Word.Application();
      }

      wordInstance.Visible = true;

      wordInstance.Documents.Add(TypeMissing, TypeMissing, TypeMissing, TypeMissing);
      var insertPoint = wordInstance.Selection;

      toBeExported.SelectAll();
      Clipboard.Clear();
      Clipboard.SetText(toBeExported.SelectedRtf, TextDataFormat.Rtf);
      insertPoint.Paste();

      wordInstance.Activate();
#endif
    }

    /// <summary>
    /// Place richtextbox content to a new Excel worksheet
    /// Caller must catch exceptions and deal with at application call point
    /// </summary>
    /// <param name="toBeExported">The RichTextBox control with text to be exported</param>
    public static void ExportRichTextToMicrosoftExcel(RichTextBox toBeExported)
    {
#if (I_HAVE_OFFICE)
      Excel.Application excelInstance;
      try
      {
        // try using an open instance if one is available
        excelInstance = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
      }
      catch (COMException)
      {
        excelInstance = new Excel.Application();
      }

      excelInstance.Workbooks.Add(Missing.Value);
      var ws = (Excel.Worksheet)excelInstance.ActiveSheet;

      var currentStart = 0;
      var currentLine = 0;
      foreach (var textLine in toBeExported.Lines)
      {
        currentLine++;
        toBeExported.SelectionStart = currentStart;
        toBeExported.SelectionLength = textLine.Length;

        // Cheat, use the clipboard to convert RTF to HTML to allow cell insertion - do this work?
        Clipboard.Clear();
        Clipboard.SetText(toBeExported.SelectedRtf, TextDataFormat.Rtf);

        // For now we try to be sneaky and see if Clipboard will convert for us.
        // If worst comes to worst, parse the BB entry format replacing [b] with <STRONG> etc.
        // Another possibility is to create a non visible word doc paste into that, then copy and paste from word to Excel.
        var myHtml = Clipboard.GetText(TextDataFormat.Html);

        // Place the html into the clipboard as the only text in the clipboard in html format.
        Clipboard.Clear();
        Clipboard.SetText(myHtml, TextDataFormat.Html);

        // select cell per line, range runs A1, A2, A3, etc, so it should place everything in the first column
        // All cells will have word wrap, width and auto fit height set.
        var pasteRange = (Excel.Range)ws.Cells["A" + currentLine, Missing.Value];
        pasteRange.Select();
        pasteRange.Columns.ColumnWidth = 300;
        pasteRange.Style.IsTextWrapped = true;
        pasteRange.PasteSpecial(Excel.XlPasteType.xlPasteValues, Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone, Missing.Value, Missing.Value);
        pasteRange.Rows.AutoFit();
        currentStart += textLine.Length;
      }

      excelInstance.Visible = true;
#endif
    }

    /// <summary>
    /// Place richtextbox content to a new OpenOffice Write Document
    /// Caller must catch exceptions and deal with at application call point
    /// </summary>
    /// <param name="toBeExported">The RichTextBox control with text to be exported</param>
    public static void ExportRichTextToOpenOfficeWrite(RichTextBox toBeExported)
    {
#if (I_HAVE_OPEN_OFFICE)
      // TODO: exportRichTextToOpenOfficeWrite
#endif
    }

    /// <summary>
    /// Place richtextbox content to a new OpenOffice Worksheet
    /// Caller must catch exceptions and deal with at application call point
    /// </summary>
    /// <param name="toBeExported">The RichTextBox control with text to be exported</param>
    public static void ExportRichTextToOpenOfficeWorksheet(RichTextBox toBeExported)
    {
#if (I_HAVE_OPEN_OFFICE)
      // TODO: exportRichTextToOpenOfficeWorksheet
#endif
    }

    /// <summary>
    /// Called after we determine that Open Office is installed to set the
    /// correct pathing environment to use the installed Open Office
    /// </summary>
    private static void InitialiseOpenOfficeEnvironment()
    {
#if (I_HAVE_OPEN_OFFICE)

      // OpenOffice being a 32 bit app, its registry location is different in a 64 bit OS
      var baseKey = (Marshal.SizeOf(typeof(IntPtr)) == 8) ? @"SOFTWARE\Wow6432Node\OpenOffice.org\" : @"SOFTWARE\OpenOffice.org\";

      // Get the URE directory
      var key = baseKey + @"Layers\URE\1";
      var reg = Registry.CurrentUser.OpenSubKey(key) ?? Registry.LocalMachine.OpenSubKey(key);

      if (reg == null) return;

      var urePath = reg.GetValue("UREINSTALLLOCATION") as string;
      reg.Close();
      if (urePath == null) return;

      urePath = Path.Combine(urePath, "bin");

      // Get the UNO Path
      key = baseKey + @"UNO\InstallPath";
      reg = Registry.CurrentUser.OpenSubKey(key) ?? Registry.LocalMachine.OpenSubKey(key);
      if (reg == null) return;

      var unoPath = reg.GetValue(null) as string;
      reg.Close();
      var path = $"{Environment.GetEnvironmentVariable("PATH")};{urePath}";
      Environment.SetEnvironmentVariable("PATH", path);
      Environment.SetEnvironmentVariable("UNO_PATH", unoPath);
#endif
    }

    /// <summary>
    /// Function to test if Microsoft Word is installed.
    /// </summary>
    /// <returns>true - if Microsoft Word was found, false - otherwise</returns>
    public static bool IsMicrosoftWordInstalled()
    {
      try
      {
        return IsWordInstalled();
      }
      catch (Exception)
      {
        return false;
      }
    }

    /// <summary>
    /// Function to test if Microsoft Excel is installed.
    /// </summary>
    /// <returns>true - if Microsoft Word was found, false - otherwise</returns>
    public static bool IsMicrosoftExcelInstalled()
    {
      try
      {
        return IsExcelInstalled();
      }
      catch (Exception)
      {
        return false;
      }
    }

    /// <summary>
    /// Function to test if Open Office is installed.
    /// </summary>
    /// <returns>true - if Open Office was found, false - otherwise</returns>
    public static bool IsOOfficeInstalled()
    {
      try
      {
        return IsOpenOfficeInstalled();
      }
      catch (Exception)
      {
        return false;
      }
    }
  }
}
